/**
 * @file helper
 * @author liaoxuezhi@baidu.com
 */
/* eslint-disable */

// 数组查找相关.
import * as assign from 'object-assign';
import * as isPlainObject from 'lodash/isPlainObject';
import * as memoize from 'lodash/memoize';
import * as clone from 'lodash/clone';

export function findIndexById(list, id) {
  return findIndexByAttrValue(list, 'id', id);
}

export function findIndexByAttrValue(list, attr, value) {
  return findIndex(list, item => String(item[attr]) === String(value));
}

export function findIndex(list, test) {
  let idx = -1;
  list.every((item, i) => !test(item) || (idx = i) && false);
  return idx;
}

export function findById(list, attr, value) {
  let idx = findIndexById(list, attr, value);
  return ~idx ? list[idx] : null;
}

export function findByAttrValue(list, attr, value) {
  let idx = findIndexByAttrValue(list, attr, value);
  return ~idx ? list[idx] : null;
}

export function find(list, test) {
  let idx = findIndex(list, test);
  return ~idx ? list[idx] : null;
}

export const uuid = (() => {
  // 来自 https://github.com/LiosK/UUID.js/blob/master/src/uuid.core.js

  return () => hex(rand(32), 8) // time_low
    + '-'
    + hex(rand(16), 4)          // time_mid
    + '-'
    + hex(0x4000 | rand(12), 4) // time_hi_and_version
    + '-'
    + hex(0x8000 | rand(14), 4) // clock_seq_hi_and_reserved clock_seq_low
    + '-'
    + hex(rand(48), 12);

  /**
   * Returns an unsigned x-bit random integer.
   * @param {int} x A positive integer ranging from 0 to 53, inclusive.
   * @returns {int} An unsigned x-bit random integer (0 <= f(x) < 2^x).
   */
  function rand(x) {  // _getRandomInt
    if (x <   0) return NaN;
    if (x <= 30) return (0 | Math.random() * (1 <<      x));
    if (x <= 53) return (0 | Math.random() * (1 <<     30))
      + (0 | Math.random() * (1 << x - 30)) * (1 << 30);
    return NaN;
  }

  /**
   * Converts an integer to a zero-filled hexadecimal string.
   * @param {int} num
   * @param {int} length
   * @returns {string}
   */
  function hex(num, length) { // _hexAligner
    let str = num.toString(16), i = length - str.length, z = '0';
    for (; i > 0; i >>>= 1, z += z) { if (i & 1) { str = z + str; } }
    return str;
  }
})();

export const isAmisSite = () => true;

let amisPortal;
export const getDialogContainer = () => {
  if (isAmisSite()) {
    return document.body;
  }

  if (!amisPortal) {
    const root = document.createElement('div');
    root.setAttribute('class', 'amis-scope amis-dialog-root');
    // root.setAttribute('style', 'position:relative');

    amisPortal = document.createElement('div');
    root.appendChild(amisPortal);
    document.body.appendChild(root);
  }

  return amisPortal;
};

let amisTooltipPortal;
export const getTooltipContainer = () => {
  if (isAmisSite()) {
    return document.body;
  }

  if (!amisTooltipPortal) {
    const root = document.createElement('div');
    root.setAttribute('class', 'amis-scope amis-tooltip-root');
    // root.setAttribute('style', 'position:relative');

    amisTooltipPortal = document.createElement('div');
    root.appendChild(amisTooltipPortal);
    document.body.appendChild(root);
  }

  return amisTooltipPortal;
};

// 没有变化的不修改。
export const updateJson = (a, b) => {
  if (Array.isArray(a) && Array.isArray(b)) {

    let newA = a.length === b.length ? null : [];
    b.forEach((item, key) => {
      if (key < a.length) {
        let ret = updateJson(a[key], item);

        if (ret !== a[key]) {
          newA = newA || a.concat();
          newA[key] = ret;
        } else {
          newA = newA || [];
          newA[key] = ret;
        }
      } else {
        newA.push(item);
      }
    });

    return newA || a;
  } else if (isPlainObject(a) && isPlainObject(b)) {
    const aKeys = Object.keys(a);
    const bKeys = Object.keys(b);

    // 只要 keys 不同就返回新的。
    if (aKeys.join('') !== bKeys.join('')) {
      return b;
    }

    let newA = null;
    bKeys.forEach(key => {
      const aValue = a[key];
      const bValue = b[key];

      let ret = updateJson(aValue, bValue);
      if (ret !== aValue) {
        newA = newA || assign({}, a);
        newA[key] = ret;
      }
    });

    return newA || a;
  }

  return a === b ? a : b;
};

export const mapFormItem = (schema, iterator, treatAsObject = false, paths = []) => {
  if (!treatAsObject && isPlainObject(schema) && schema.type && schema.name) {
    return iterator(mapFormItem(schema, iterator, true, paths), paths);
  } else if (isPlainObject(schema)) {
    let newSchema = null;
    let subPaths = paths.concat();
    subPaths.unshift(schema);
    Object.keys(schema).forEach(key => {
      let newValue = mapFormItem(schema[key], iterator, false, subPaths);

      if (newValue !== schema[key]) {
        newSchema = newSchema || assign({}, schema);
        newSchema[key] = newValue;
      }
    });
    return newSchema || schema;
  } else if (Array.isArray(schema)) {
    return schema.map(item => mapFormItem(item, iterator, false, paths));
  } else {
    return schema;
  }
};

export const mergeFormSchema = (a, b, isFormItemCollections = false) => {
  if (Array.isArray(a) && Array.isArray(b)) {

    if (!isFormItemCollections) {
      return b;
    }

    let newA = a.concat();

    b.forEach((item, key) => {
      if (Array.isArray(item) && newA[key]) {
        newA[key] = mergeFormSchema(newA[key], item, true);
      } else if (item.type || item.name) {
        let oKey = findIndexByAttrValue(a, 'name', item.name);
        if (~oKey) {
          newA[oKey] = mergeFormSchema(newA[oKey], item);
        } else {
          newA.push(item);
        }
      } else {
        newA.push(item);
      }
    });

    return newA || a;
  } else if (isPlainObject(a) && isPlainObject(b)) {
    const bKeys = Object.keys(b);

    let newA = assign({}, a);
    bKeys.forEach(key => {
      const aValue = a[key];
      const bValue = b[key];

      let ret = mergeFormSchema(aValue, bValue, key === 'controls');
      if (ret !== aValue) {
        newA[key] = ret;
      }
    });

    return newA;
  }

  return a === b ? a : b;
};

export const getIfSecondDomain = memoize(() => /^(?:([^.]*)\.)+amis\.baidu\.com$/.test(location.hostname) ? RegExp.$1 : false);
export const getIfHoutaiDomain = memoize(() => /^houtai[^.]*\.baidu\.com$/.test(location.hostname));


// xs < 768px
// sm >= 768px
// md >= 992px
// lg >= 1200px
export function isBreakpoint(str) {
  if (!str) {
    return false;
  }

  const breaks = str.split(/\s+/);

  if (window.matchMedia) {
    return breaks.some(item =>
      item === '*'
      || item === 'xs' && matchMedia(`screen and (max-width: 767px)`).matches
      || item === 'sm' && matchMedia(`screen and (min-width: 768px) and (max-width: 991px)`).matches
      || item === 'md' && matchMedia(`screen and (min-width: 992px) and (max-width: 1199px)`).matches
      || item === 'lg' && matchMedia(`screen and (min-width: 1200px)`).matches
    )

  } else {
    const width = window.innerWidth;
    return breaks.some(item =>
      item === '*'
      || item === 'xs' && width < 768
      || item === 'sm' && width >= 768 && width < 992
      || item === 'md' && width >= 992 && width < 1200
      || item === 'lg' && width >= 1200
    );
  }
}

function customMerge(a, b) {
  if (isPlainObject(a) && isPlainObject(b)) {
    const obj = clone(a);

    Object.keys(b).forEach(key => {
      obj[key] = customMerge(obj[key], b[key]);
    });

    return obj;
  }

  return typeof b === 'undefined' ? a : b;
}

export function merge(...args) {
  const src = args.shift();
  return args.reduce((src, dst) => customMerge(src, dst), clone(src));
}

export function closest(element, selector, context) {
  element = { parentNode: element };

  while ((element = element.parentNode) && element !== context) {
    if (matches(element, selector)) {
      return element;
    }
  }
}

export function matches(elem, selector) {
  // Vendor-specific implementations of `Element.prototype.matches()`.
  let proto = window.Element.prototype;
  let nativeMatches = proto.matches ||
    proto.mozMatchesSelector ||
    proto.msMatchesSelector ||
    proto.oMatchesSelector ||
    proto.webkitMatchesSelector;

  if (!elem || elem.nodeType !== 1) {
    return false;
  }

  let parentElem = elem.parentNode;

  // use native 'matches'
  if (nativeMatches) {
    return nativeMatches.call(elem, selector);
  }

  // native support for `matches` is missing and a fallback is required
  let nodes = parentElem.querySelectorAll(selector);
  let len = nodes.length;

  for (let i = 0; i < len; i++) {
    if (nodes[i] === elem) {
      return true;
    }
  }

  return false;
}
